
local alien = require 'alien'
local alienex = require 'alienex'
local config = require 'config'
local ipairs, unpack, print
    = ipairs, unpack, print
local tremove, tinsert
    = table.remove, table.insert


module (...)


JPEG_LIB_VERSION = 62

DCTSIZE	= 8
DCTSIZE2 = 64
NUM_QUANT_TBLS = 4
NUM_HUFF_TBLS = 4
NUM_ARITH_TBLS = 16
MAX_COMPS_IN_SCAN = 4
MAX_SAMP_FACTOR = 4
C_MAX_BLOCKS_IN_MCU = 10
D_MAX_BLOCKS_IN_MCU = 10

local lib = alien.load(config.libjpeg_path)

JMSG_LENGTH_MAX = 200
JMSG_STR_PARM_MAX = 80

local function expand_def(def)
  local i = 1
  while def[i] do
    local v = def[i]
    if v[3] then
      tremove(def, i)
      for j = 1,v[3] do
        tinsert(def, i, {v[1] .. "_" .. j, v[2]})
        i = i + 1
      end
    else
      i = i + 1
    end
  end
  return def
  
end

jpeg_error_mgr = alien.defstruct(expand_def{
  -- Error exit handler: does not return to caller
  {"error_exit", "callback" --[[JMETHOD(void, error_exit, (j_common_ptr cinfo))]]};
  -- Conditionally emit a trace or warning message
  {"emit_message", "callback" --[[JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level))]]};
  -- Routine that actually outputs a trace or error message
  {"output_message", "callback" --[[JMETHOD(void, output_message, (j_common_ptr cinfo))]]};
  -- Format a message string for the most recent JPEG error or message
  {"format_message", "callback" --[[JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer))]]};
  -- Reset error state variables at start of a new image */
  {"reset_error_mgr", "callback" --[[JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo))]]};
  
  -- The message ID code and any parameters are saved here.
  -- A message can have one string parameter or up to 8 int parameters.
  {"msg_code", "int"};
  
  {"s", "char", JMSG_STR_PARM_MAX};
  
  -- Standard state variables for error facility
  {"trace_level", "int"}; -- max msg_level that will be displayed
  
  --[[
   For recoverable corrupt-data errors, we emit a warning message,
    but keep going unless emit_message chooses to abort.  emit_message
    should count warnings in num_warnings.  The surrounding application
    can check for bad data by seeing if num_warnings is nonzero at the
    end of processing.
  --]]
  {"num_warnings", "long"}; -- number of corrupt-data warnings

  --[[
   * These fields point to the table(s) of error message strings.
   * An application can change the table pointer to switch to a different
   * message list (typically, to change the language in which errors are
   * reported).  Some applications may wish to add additional error codes
   * that will be handled by the JPEG library error mechanism; the second
   * table pointer is used for this purpose.
   *
   * First table includes all errors generated by JPEG library itself.
   * Error code 0 is reserved for a "no such error string" message.
   *
  --]]
  {"jpeg_message_table", "pointer" --[[const char * const *]]};
  {"last_jpeg_message", "int"}; -- Table contains strings 0..last_jpeg_message
  --[[
   Second table can be added by application (see cjpeg/djpeg for example).
   It contains strings numbered first_addon_message..last_addon_message.
  --]]
  {"addon_message_table", "pointer" --[[const char * const *]]};
  {"first_addon_message", "int"}; -- code for first string in addon table
  {"last_addon_message", "int"}; -- code for last string in addon table
})

jpeg_decompress_struct = alien.defstruct(expand_def{
  {"err", "pointer" --[[jpeg_error_mgr*]]};
  {"mem", "pointer" --[[jpeg_memory_mgr*]]};
  {"progress", "pointer" --[[jpeg_progress_mgr*]]};
  {"client_data", "pointer" --[[void*]]};
  {"is_decompressor", "char"};
  {"global_state", "int"};
  {"src", "pointer" --[[jpeg_source_mgr*]]};
  {"image_width", "uint"};
  {"image_height", "uint"};
  {"num_components", "int"};
  {"jpeg_color_space", "int"};
  {"out_color_space", "int"};
  {"scale_num", "uint"};
  {"scale_denom", "uint"};
  {"output_gamma", "double"};
  {"buffered_image", "char"};
  {"raw_data_out", "char"};
  {"dct_method", "int"};
  {"do_fancy_upsampling", "char"};
  {"do_block_smoothing", "char"};
  {"quantize_colors", "char"};
  {"dither_mode", "int"};
  {"two_pass_quantize", "char"};
  {"desired_number_of_colors", "int"};
  {"enable_1pass_quant", "char"};
  {"enable_external_quant", "char"};
  {"enable_2pass_quant", "char"};
  {"output_width", "int"};
  {"output_height", "int"};
  {"out_color_components", "int"};
  {"output_components", "int"};
  {"rec_outbuf_height", "int"};
  {"actual_number_of_colors", "int"};
  {"colormap", "pointer" --[[JSAMPARRAY]]};
  {"output_scanline", "uint" --[[JDIMENSION]]};
  {"input_scan_number", "int"};
  {"input_iMCU_row", "uint" --[[JDIMENSION]]};
  {"output_scan_number", "int"};
  {"output_iMCU_row", "uint" --[[JDIMENSION]]};
  {"coef_bits", "pointer" --[[int*]], DCTSIZE2};
  {"quant_tbl_ptrs", "pointer" --[[JQUANT_TBL*]], NUM_QUANT_TBLS};
  {"dc_huff_tbl_ptrs", "pointer" --[[JHUFF_TBL*]], NUM_HUFF_TBLS};
  {"ac_huff_tbl_ptrs", "pointer" --[[JHUFF_TBL*]], NUM_HUFF_TBLS};
  {"data_precision", "int"};
  {"comp_info", "pointer" --[[jpeg_component_info *]]};
  {"progressive_mode", "char"};
  {"arith_code", "char"};
  {"arith_dc_L", "char", NUM_ARITH_TBLS};
  {"arith_dc_U", "char", NUM_ARITH_TBLS};
  {"arith_dc_K", "char", NUM_ARITH_TBLS};
  {"restart_interval", "uint"};
  {"saw_JFIF_marker", "char"};
  {"JFIF_major_version", "char"};
  {"JFIF_minor_version", "char"};
  {"density_unit", "char"};
  {"X_density", "ushort"};
  {"Y_density", "ushort"};
  {"saw_Adobe_marker", "char"};
  {"Adobe_transform", "char"};
  {"CCIR601_sampling", "char"};
  {"marker_list", "pointer" --[[jpeg_saved_marker_ptr]]};
  {"max_h_samp_factor", "int"};
  {"max_v_samp_factor", "int"};
  {"min_DCT_scaled_size", "int"};
  {"total_iMCU_rows", "uint"};
  {"sample_range_limit", "pointer" --[[JSAMPLE *]]};
  {"comps_in_scan", "int"};
  {"cur_comp_info", "pointer" --[[jpeg_component_info*]], MAX_COMPS_IN_SCAN};
  {"MCUs_per_row", "uint"};
  {"MCU_rows_in_scan", "uint"};
  {"blocks_in_MCU", "int"};
  {"MCU_membership", "int", D_MAX_BLOCKS_IN_MCU};
  {"Ss", "int"}; {"Se", "int"}; {"Ah", "int"}; {"Al", "int"};
  {"unread_marker", "int"};
  {"master", "pointer" --[[struct jpeg_decomp_master *]]};
  {"main", "pointer" --[[struct jpeg_d_main_controller *]]};
  {"coef", "pointer" --[[struct jpeg_d_coef_controller *]]};
  {"post", "pointer" --[[struct jpeg_d_post_controller *]]};
  {"inputctl", "pointer" --[[struct jpeg_input_controller *]]};
  {"market", "pointer" --[[struct jpeg_marker_reader *]]};
  {"entropy", "pointer" --[[struct jpeg_entropy_decoder *]]};
  {"idct", "pointer" --[[struct jpeg_inverse_dct *]]};
  {"upsample", "pointer" --[[struct jpeg_upsampler *]]};
  {"cconvert", "pointer" --[[struct jpeg_color_deconverter *]]};
  {"cquantize", "pointer" --[[struct jpeg_color_quantizer *]]};
})

jpeg_std_error = lib.jpeg_std_error
jpeg_std_error:types{ ret="pointer" --[[jpeg_error_mgr*]]; "pointer" --[[jpeg_error_mgr*]] }

jpeg_CreateDecompress = lib.jpeg_CreateDecompress
jpeg_CreateDecompress:types{ ret="void"; abi="stdcall";
  "pointer" --[[j_decompress_ptr cinfo]], "int" --[[version]], alienex.size_t --[[size_t structsize]] }
  
local JPEG_DECOMPRESS_STRUCT_SIZE = 432

function jpeg_create_decompress(cinfo)
  return jpeg_CreateDecompress(cinfo, JPEG_LIB_VERSION, JPEG_DECOMPRESS_STRUCT_SIZE)
end

jpeg_destroy_decompress = lib.jpeg_destroy_decompress
jpeg_destroy_decompress:types{ ret="void"; "pointer" --[[j_decompress_ptr]] }

jpeg_stdio_src = lib.jpeg_stdio_src
jpeg_stdio_src:types{ ret="void"; "pointer" --[[j_decompress_ptr cinfo]], "pointer" --[[FILE* infile]] }

jpeg_read_header = lib.jpeg_read_header
jpeg_read_header:types{ ret="int"; "pointer" --[[j_decompress_ptr cinfo]], "char" --[[boolean require_image]] }

jpeg_start_decompress = lib.jpeg_start_decompress
jpeg_start_decompress:types{ ret="char" --[[boolean]]; "pointer" --[[j_decompress_ptr cinfo]] }

jpeg_read_scanlines = lib.jpeg_read_scanlines
jpeg_read_scanlines:types{ ret="uint" --[[JDIMENSION]];
  "pointer" --[[j_decompress_ptr cinfo]], "pointer" --[[JSAMPARRAY scanlines]], "uint" --[[max_lines]] }
jpeg_finish_decompress = lib.jpeg_finish_decompress
jpeg_finish_decompress:types{ ret="char" --[[boolean]]; "pointer" --[[j_decompress_ptr cinfo]] }

jpeg_source_mgr = alien.defstruct{
  {"next_input_byte", "pointer" --[[const JOCTET*]]};  -- => next byte to read from buffer
  {"bytes_in_buffer", alienex.size_t}; -- # of bytes remaining in buffer
  
  {"init_source", "callback" --[[JMETHOD(void, init_source, (j_decompress_ptr cinfo))]]};
  {"fill_input_buffer", "callback" --[[JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo))]]};
  {"skip_input_data", "callback" --[[JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes))]]};
  {"resync_to_restart", "callback" --[[JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired))]]};
  {"term_source", "callback" --[[JMETHOD(void, term_source, (j_decompress_ptr cinfo))]]};
}
jpeg_source_mgr.init_source_types       = {ret="void";                "pointer" --[[j_decompress_ptr]]}
jpeg_source_mgr.fill_input_buffer_types = {ret="char" --[[boolean]];  "pointer" --[[j_decompress_ptr]]}
jpeg_source_mgr.skip_input_data_types   = {ret="void";                "pointer" --[[j_decompress_ptr]], "long" --[[num_bytes]]}
jpeg_source_mgr.resync_to_restart_types = {ret="char" --[[boolean]];  "pointer" --[[j_decompress_ptr]], "int" --[[desired]]}
jpeg_source_mgr.term_source_types       = {ret="void";                "pointer" --[[j_decompress_ptr]]}
